/*
 *    Copyright © OpenAtom Foundation.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package com.inspur.edp.bef.core.lock;

import com.inspur.edp.bef.api.befruntimeconfig.RuntimeConService;
import com.inspur.edp.bef.core.DotNetToJavaStringHelper;
import com.inspur.edp.bef.core.lock.LockAdapter.LockEntity;
import com.inspur.edp.bef.core.session.transaction.IBefTransactionItem;
import com.inspur.edp.cdp.common.utils.spring.SpringUtil;
import com.inspur.edp.cef.api.RefObject;
import com.inspur.edp.commonmodel.core.session.distributed.RootEditToken;
import com.inspur.edp.commonmodel.core.session.distributed.dac.FuncSessionItemIncrement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import lombok.var;

//成功后合并模式
@Slf4j
public class LockServiceItem implements IBefTransactionItem {

   private final String beType;
   private final LockServiceItem upper;

   private java.util.List<GroupDataIdsPair> baseLocks;
   private java.util.List<GroupDataIdsPair> currentLocks;
   private RootEditToken token;

   private java.util.List<GroupDataIdsPair> getLocks() {
      if (currentLocks == null) {
         if (baseLocks == null) {
            baseLocks = new ArrayList<>();
         }
         currentLocks = new ArrayList<>(baseLocks);
      }
      return currentLocks;
   }

   public LockServiceItem(String beType) {
      this(beType, null);
   }

   public LockServiceItem(String beType, LockServiceItem upper) {
      this.beType = beType;
      this.upper = upper;
   }

   public boolean getLockState(String dataId, RefObject<String> userName) {
      for (GroupDataIdsPair groupLock : getLocks()) {
         if (groupLock.getDataIds().contains(dataId)) {
            userName.argvalue = null;
            return true;
         } else if (groupLock.getFailedDataIds().containsKey(dataId)) {
            userName.argvalue = groupLock.getFailedDataIds().get(dataId);
            return false;
         }
      }

      if (upper != null) {
         return upper.getLockState(dataId, userName);
      } else {
         userName.argvalue = null;
         return false;
      }
   }

   //region add lock

   /**
    * 新增锁
    */
   public void addLock(String dataId) {
      ArrayList list = new java.util.ArrayList<String>(1);
      list.add(dataId);
      addLocks(list);
   }

   /**
    * 新增锁
    */
   public boolean addLocks(java.util.List<String> dataIds) {
      //获取未加锁的Id列表
      List<String> unlockedDataIds = getUnlockIds(dataIds);
      if (unlockedDataIds.isEmpty()) {
         return true;
      }
      GroupDataIdsPair beLocks = getLocks().stream().findFirst().orElseGet(() -> {
         var rez = new GroupDataIdsPair();
         getLocks().add(rez);
         return rez;
      });
      //调用caf加锁组件
      boolean success;
      java.util.List<LockAdapter.LockEntity> lockEntities = null;
      if(LockAdapter.getInstance().isMobileClient()){
         success = !LockAdapter.getInstance().isLocked(beType, unlockedDataIds);
      } else {
         if (DotNetToJavaStringHelper.isNullOrEmpty(beLocks.getGroupId())) {
            RefObject<java.util.List<LockEntity>> tempRef_lockEntities = new RefObject<>(
                lockEntities);
            RefObject<String> groupId = new RefObject("");

            success = LockAdapter.getInstance()
                .addLock(beType, unlockedDataIds, groupId, tempRef_lockEntities);
            lockEntities = tempRef_lockEntities.argvalue;
            if (success) {
               beLocks.setGroupId(groupId.argvalue);
            }
         } else {
            RefObject<java.util.List<LockEntity>> tempRef_lockEntities2 = new RefObject<>(
                lockEntities);
            success = LockAdapter.getInstance()
                .addLock2Group(beType, unlockedDataIds, beLocks.getGroupId(),
                    tempRef_lockEntities2);
            lockEntities = tempRef_lockEntities2.argvalue;
         }
         //兼容自己锁自己
         if(!success){
            RuntimeConService service = SpringUtil.getBean(RuntimeConService.class);
            if(service.isUseIgnoreLock()) {
               success = LockAdapter.getInstance().addIgnoreLock(beType, unlockedDataIds, beLocks.getGroupId());
            }
         }
      }
      if (success) {
         unlockedDataIds.forEach(id -> beLocks.getDataIds().add(id));
         if(token != null) {
            FuncSessionItemIncrement inc = token.getItemIncrement(beType);
            if(inc.getLockIds() == null) {
               inc.setLockIds(new ArrayList<>());
            }
            inc.getLockIds().add(beLocks.getGroupId());
         }
      } else if (lockEntities != null) {
         for (LockAdapter.LockEntity entity : lockEntities) {
            beLocks.getFailedDataIds().put(entity.getDataId(), entity.getUserName());
         }
      }
      return success;
   }

   private List<String> getUnlockIds(List<String> dataIds) {
      List<String> rez = new ArrayList<>(dataIds);
      return innerGetUnlockIds(rez);
   }

   private List<String> innerGetUnlockIds(List<String> dataIds) {
      for (GroupDataIdsPair pair : getLocks()) {
         if (pair.getDataIds().isEmpty()) {
            continue;
         }
         dataIds.removeIf(item -> pair.getDataIds().contains(item));
      }
      if (upper != null) {
         return upper.innerGetUnlockIds(dataIds);
      } else {
         return dataIds;
      }
   }
   //endregion

   //region remove lock
   public void releaseLocks() {
      //删除全部锁, 在保存成功后/cancel/cleanup 调用, 此类行为穿透所有事务.
      releaseCurrentItemLock();
      if (upper != null) {
         upper.releaseLocks();
      } else if (token != null) {
         FuncSessionItemIncrement inc = token.getItemIncrement(beType);
         inc.setLockIds(null);
      }
   }

   private void releaseCurrentItemLock() {
      if (currentLocks != null) {
         for (GroupDataIdsPair item : currentLocks) {
            if (item.getGroupId() != null) {
               LockAdapter.getInstance().releaseLock(item.getGroupId());
            }
         }
         currentLocks = null;
         baseLocks = null;
      } else if (baseLocks != null) {
         for (GroupDataIdsPair item : baseLocks) {
            if (item.getGroupId() != null) {
               LockAdapter.getInstance().releaseLock(item.getGroupId());
            }
         }
         baseLocks = null;
      }
   }
   //endregion

   //region actionTransaction
   public void rejectCurrentLocks() {
      if (currentLocks == null) {
         return;
      }
      if (currentLocks != null) {
         currentLocks.stream().filter(item -> !baseLocks.contains(item)).forEach(item -> {
            if (item.getGroupId() != null) {
               LockAdapter.getInstance().releaseLock(item.getGroupId());
            }
         });
         currentLocks = null;
      }
   }

   public void acceptCurrentLocks() {
      if(currentLocks != null) {
         baseLocks = currentLocks;
         currentLocks = null;
      }
   }
   //endregion

   //region Transaction
   @Override
   public void commit(IBefTransactionItem upper) {
      List<GroupDataIdsPair> locks = ((LockServiceItem) upper).getLocks();
      if (token != null) {
         if(locks != null && !locks.isEmpty()) {
            token.getItemIncrement(beType).setLockIds(
                locks.stream().map(item -> item.getGroupId()).filter(item -> item != null)
                    .collect(Collectors.toList()));
         }
      }
      getLocks().addAll(locks);
   }

   @Override
   public void rollBack() {
      releaseCurrentItemLock();
   }

   @Override
   public LockServiceItem begin() {
      return new LockServiceItem(beType, this);
   }

   @Override
   public void beginEdit(RootEditToken token) {
      this.token = token;
   }

   @Override
   public void notifySave() {
   }

   @Override
   public void endEdit(RootEditToken token) {
      this.token = null;
   }

   //region tcc
   public String upgradeLock4Tcc() {
      //TODO:暂时先解context锁再加appInstance锁, 此处希望由锁组件提供接口直接.
      List<GroupDataIdsPair> locks = getLocks();
      if(locks.isEmpty())
         return null;
      ArrayList<String> dataIds = new ArrayList<>();
      locks.forEach(item -> dataIds.addAll(item.getDataIds()));
      RefObject<String> groupId = new RefObject<>(null);
      releaseLocks();
      if(!LockAdapter.getInstance().addTccLock(beType, dataIds, groupId))  {
         throw new RuntimeException(String.format("be[%s]上的数据锁tcc升级失败", beType));
      } else {
         Objects.requireNonNull(groupId.argvalue);
         return groupId.argvalue;
      }
   }

   public void initLockState4TccSecondPhase(String groupId, List<String> dataIds){
      getLocks().add(new GroupDataIdsPair(){{setGroupId(groupId); getDataIds().addAll(dataIds);}});
   }

   public void recover(FuncSessionItemIncrement increment) {
//      if(increment.isReset()) {
//         if(baseLocks != null)
//            baseLocks = null;
//         if(currentLocks != null)
//            currentLocks = null;
//      }
      if(increment.getLockIds() != null) {
         if (baseLocks == null) {
            baseLocks = new ArrayList<>();
            ((ArrayList)baseLocks).ensureCapacity(increment.getLockIds().size());
         }
         for (String id : increment.getLockIds()) {
            GroupDataIdsPair pair = new GroupDataIdsPair();
            pair.setGroupId(id);
            pair.getDataIds().addAll(LockAdapter.getInstance().getDataIdsByGroupId(id));
            baseLocks.add(pair);
         }
         currentLocks = null;
      }
   }

   public void reset() {
      if (baseLocks != null) {
         baseLocks = null;
      }
      if (currentLocks != null) {
         currentLocks = null;
      }
   }
  //endregion
   //endregion

   //region innner class GroupDataIdsPair
   private static class GroupDataIdsPair {

      private String privateGroupId;

      public final String getGroupId() {
         return privateGroupId;
      }

      public final void setGroupId(String value) {
         privateGroupId = value;
      }

      private java.util.HashSet<String> privateDataIds = new java.util.HashSet<String>();

      public final java.util.HashSet<String> getDataIds() {
         return privateDataIds;
      }

      //<dataId, userName>
      private java.util.HashMap<String, String> privateFailedDataIds = new java.util.HashMap<String, String>();

      public final java.util.HashMap<String, String> getFailedDataIds() {
         return privateFailedDataIds;
      }

   }
   //endregion
}
